﻿using UnityEngine;
using System;

namespace Obi
{
    public class ComputeParticleGrid : IDisposable
    {
        public ComputeShader gridShader;
        public int clearKernel;
        public int insertSimplicesKernel;
        public int gridPopulationKernel;
        public int sortSimplicesKernel;
        public int buildFluidDispatchKernel;
        public int buildMortonKernel;
        public int mortonSortKernel;
        public int sortFluidSimplicesKernel;

        public int sameLevelNeighborsKernel;
        public int upperLevelsNeighborsKernel;
        public int buildFluidIndexBufferKernel;
        public int contactsKernel;

        public ComputePrefixSum cellsPrefixSum;
        private ComputePrefixSum fluidPrefixSum;

        public GraphicsBuffer cellCounts;                   // for each cell, how many simplices in it.
        public GraphicsBuffer cellOffsets;                  // index of the first simplex in the cell (on gridToMorton buffer).
        public GraphicsBuffer offsetInCell;                 // for each simplex, offset within its cell.
        public GraphicsBuffer levelPopulation;              // buffer storing amount of entries in each grid level

        public GraphicsBuffer neighbors;                    // neighbor indices for all particles. (maxNeighbors * simplexCount in size)
        public GraphicsBuffer neighborCounts;               // amount of neighbors for each particle. (simplexCount in size)

        public GraphicsBuffer contactPairs;                 // list of particle pairs.
        public GraphicsBuffer dispatchBuffer;               // dispatch info for iterating trough contacts.

        public GraphicsBuffer cellHashToMortonIndex;
        public GraphicsBuffer mortonSortedCellHashes;       // contains all cell hashes sorted according to their morton index.
        public GraphicsBuffer sortedSimplexIndices;         // maps from grid index to simplex index.

        public GraphicsBuffer sortedFluidIndices;           // contains compacted sorted indices of fluid simplices *only*. Eg, entry 0 contains the index of the first fluid simplex along the morton curve.
        public GraphicsBuffer sortedSimplexToFluid;         // maps from sorted simplex index to sorted fluid index, or -1 if the simplex is not fluid.

        public GraphicsBuffer sortedPrincipalRadii;         // Used by Density constraints: needs to be sorted once per step (not changed by constraints)
        public GraphicsBuffer sortedFluidMaterials;         // Used by Density constraints: needs to be sorted once per step (not changed by constraints)
        public GraphicsBuffer sortedFluidInterface;         // Used by Density constraints: needs to be sorted once per step (not changed by constraints)
        public GraphicsBuffer sortedFluidData;              // Used by Density constraints: no need to be sorted (constraints output).

        public GraphicsBuffer sortedLinearVel;              // Not used by density constraints: used for foam advection and mesher.
        public GraphicsBuffer sortedAngularVel;             // Not used by density constraints: used for foam advection.

        public GraphicsBuffer sortedPositions;              // Used by Density constraints: needs to be sorted once per iteration.
        public GraphicsBuffer sortedPrevPosOrientations;    // Used by Density constraints: needs to be sorted once per iteration. Reuse for orientations (foam advection and mesher).
        public GraphicsBuffer sortedUserDataColor;          // Used by Density constraints: needs to be sorted once per iteration. Reuse for colors (mesher).

        private const int maxGridLevels = 24;
        private uint[] clearDispatch = { 0, 1, 1, 0 };

        public int maxParticleNeighbors { get; private set; } = 128;
        public int maxParticleContacts { get; private set; } = 4;

        public int maxCells{
            get { return cellCounts != null ? cellCounts.count : 0; }
        }

        public ComputeParticleGrid()
        {
            gridShader = GameObject.Instantiate(Resources.Load<ComputeShader>("Compute/ParticleGrid"));
            clearKernel = gridShader.FindKernel("Clear");
            insertSimplicesKernel = gridShader.FindKernel("InsertSimplices");
            gridPopulationKernel = gridShader.FindKernel("FindPopulatedLevels");
            sortSimplicesKernel = gridShader.FindKernel("SortSimplices");
            buildFluidDispatchKernel = gridShader.FindKernel("BuildFluidDispatch");
            buildMortonKernel = gridShader.FindKernel("BuildMortonIndices");
            mortonSortKernel = gridShader.FindKernel("MortonSort");
            sortFluidSimplicesKernel = gridShader.FindKernel("SortFluidSimplices");
            sameLevelNeighborsKernel = gridShader.FindKernel("FindFluidNeighborsInSameLevel");
            upperLevelsNeighborsKernel = gridShader.FindKernel("FindFluidNeighborsInUpperLevels");
            buildFluidIndexBufferKernel = gridShader.FindKernel("BuildFluidParticleIndexBuffer");
            contactsKernel = gridShader.FindKernel("BuildContactList");

            levelPopulation = new GraphicsBuffer(GraphicsBuffer.Target.Structured, maxGridLevels + 1, 4);
            dispatchBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 4, sizeof(uint));
        }

        private void Clear()
        {
            cellsPrefixSum?.Dispose();
            fluidPrefixSum?.Dispose();

            if (cellCounts != null)
                cellCounts.Dispose();
            if (cellOffsets != null)
                cellOffsets.Dispose();
            if (offsetInCell != null)
                offsetInCell.Dispose();

            if (contactPairs != null)
                contactPairs.Dispose();

            if (neighbors != null)
                neighbors.Dispose();
            if (neighborCounts != null)
                neighborCounts.Dispose();

            if (cellHashToMortonIndex != null)
                cellHashToMortonIndex.Dispose();
            if (mortonSortedCellHashes != null)
                mortonSortedCellHashes.Dispose();
            if (sortedSimplexIndices != null)
                sortedSimplexIndices.Dispose();
            if (sortedFluidIndices != null)
                sortedFluidIndices.Dispose();
            if (sortedSimplexToFluid != null)
                sortedSimplexToFluid.Dispose();

            if (sortedPositions != null)
                sortedPositions.Dispose();
            if (sortedPrevPosOrientations != null)
                sortedPrevPosOrientations.Dispose();
            if (sortedPrincipalRadii != null)
                sortedPrincipalRadii.Dispose();
            if (sortedFluidMaterials != null)
                sortedFluidMaterials.Dispose();
            if (sortedFluidInterface != null)
                sortedFluidInterface.Dispose();
            if (sortedUserDataColor != null)
                sortedUserDataColor.Dispose();
            if (sortedFluidData != null)
                sortedFluidData.Dispose();

            if (sortedLinearVel != null)
                sortedLinearVel.Dispose();
            if (sortedAngularVel != null)
                sortedAngularVel.Dispose();

            cellsPrefixSum = null;
            fluidPrefixSum = null;
            cellCounts = null;
            cellOffsets = null;
            offsetInCell = null;
            contactPairs = null;
            neighbors = null;
            neighborCounts = null;
            sortedSimplexIndices = null;
            cellHashToMortonIndex = null;
            sortedFluidIndices = null;
            sortedSimplexToFluid = null;
            sortedPositions = null;
            sortedPrevPosOrientations = null;
            sortedPrincipalRadii = null;
            sortedFluidMaterials = null;
            sortedFluidInterface = null;
            sortedUserDataColor = null;
            sortedFluidData = null;
            sortedLinearVel = null;
            sortedAngularVel = null;
        }

        public void Dispose()
        {
            if (levelPopulation != null)
                levelPopulation.Dispose();

            if (dispatchBuffer != null)
                dispatchBuffer.Dispose();

            levelPopulation = null;
            dispatchBuffer = null;

            Clear();
        }

        // Sets the maximum amount of items that can be stored in the grid.
        public bool SetCapacity(int capacity, uint maxParticleContacts, uint maxParticleNeighbors)
        {
            if (offsetInCell == null || capacity > offsetInCell.count ||
                maxParticleNeighbors != this.maxParticleNeighbors ||
                maxParticleContacts != this.maxParticleContacts)
            {
                Clear();

                this.maxParticleNeighbors = (int)maxParticleNeighbors;
                this.maxParticleContacts = (int)maxParticleContacts;

                capacity *= 2;
                int hashtableSize = (int)(capacity * 1.5f);

                // hashtable data:
                cellCounts = new GraphicsBuffer(GraphicsBuffer.Target.Structured, hashtableSize, 4);
                cellOffsets = new GraphicsBuffer(GraphicsBuffer.Target.Structured, hashtableSize, 4);
                mortonSortedCellHashes = new GraphicsBuffer(GraphicsBuffer.Target.Structured, hashtableSize, 4);
                cellHashToMortonIndex = new GraphicsBuffer(GraphicsBuffer.Target.Structured, hashtableSize, 4);
                cellsPrefixSum = new ComputePrefixSum(hashtableSize);

                // per simplex data:
                offsetInCell = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 4);
                sortedSimplexIndices = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 4);
                sortedFluidIndices = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 4);
                sortedSimplexToFluid = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 4);

                // contact pairs and neighbor lists:
                contactPairs = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity * (int)maxParticleContacts, 8);
                neighbors = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity * (int)maxParticleNeighbors, 4);
                neighborCounts = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 4);

                // sorted fluid simplex data:
                fluidPrefixSum = new ComputePrefixSum(capacity);
                sortedPrincipalRadii = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 16);
                sortedFluidMaterials = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 16);
                sortedFluidInterface = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 16);
                sortedPositions = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 16);
                sortedPrevPosOrientations = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 16);
                sortedUserDataColor = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 16);
                sortedFluidData = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 16);
                sortedLinearVel = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 16);
                sortedAngularVel = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, 16);

                return true;
            }
            return false;
        }

        public void BuildGrid(ComputeSolverImpl solver, float deltaTime)
        {
            dispatchBuffer.SetData(clearDispatch);
            solver.fluidDispatchBuffer.SetData(clearDispatch);

            if (solver.simplexCounts.simplexCount > 0)
            {
                gridShader.SetInt("pointCount", solver.simplexCounts.pointCount);
                gridShader.SetInt("edgeCount", solver.simplexCounts.edgeCount);
                gridShader.SetInt("triangleCount", solver.simplexCounts.triangleCount);
                gridShader.SetInt("maxContacts", contactPairs.count);
                gridShader.SetInt("maxCells", cellCounts.count);
                gridShader.SetInt("maxNeighbors", maxParticleNeighbors);
                gridShader.SetFloat("deltaTime", deltaTime);
                gridShader.SetFloat("collisionMargin", solver.abstraction.parameters.collisionMargin);
                gridShader.SetFloat("particleCCD", solver.abstraction.parameters.particleCCD);
                int cellThreadGroups = ComputeMath.ThreadGroupCount(cellCounts.count, 128);

                solver.abstraction.particleContacts.computeBuffer.SetCounterValue(0);

                int simplexThreadGroups = ComputeMath.ThreadGroupCount(solver.simplexCounts.simplexCount, 128);

                // clear grid:
                gridShader.SetBuffer(clearKernel, "cellCounts", cellCounts);
                gridShader.SetBuffer(clearKernel, "cellOffsets", cellOffsets);
                gridShader.SetBuffer(clearKernel, "levelPopulation", levelPopulation);
                gridShader.SetBuffer(clearKernel, "mortonSortedCellHashes", mortonSortedCellHashes);
                gridShader.Dispatch(clearKernel, cellThreadGroups, 1, 1);

                // insert simplices in grid and flag fluid simplices:
                gridShader.SetBuffer(insertSimplicesKernel, "solverBounds", solver.reducedBounds);
                gridShader.SetBuffer(insertSimplicesKernel, "simplexBounds", solver.simplexBounds);
                gridShader.SetBuffer(insertSimplicesKernel, "simplices", solver.simplices);
                gridShader.SetBuffer(insertSimplicesKernel, "phases", solver.phasesBuffer);
                gridShader.SetBuffer(insertSimplicesKernel, "neighborCounts", neighborCounts);
                gridShader.SetBuffer(insertSimplicesKernel, "levelPopulation", levelPopulation);
                gridShader.SetBuffer(insertSimplicesKernel, "cellCoords", solver.cellCoordsBuffer);
                gridShader.SetBuffer(insertSimplicesKernel, "cellCounts", cellCounts);
                gridShader.SetBuffer(insertSimplicesKernel, "offsetInCell", offsetInCell);
                gridShader.SetBuffer(insertSimplicesKernel, "cellOffsets", cellOffsets);
                gridShader.Dispatch(insertSimplicesKernel, simplexThreadGroups, 1, 1);

                // find populated grid levels:
                gridShader.SetBuffer(gridPopulationKernel, "levelPopulation", levelPopulation);
                gridShader.Dispatch(gridPopulationKernel, 1, 1, 1);

                // sort cells along morton curve:
                gridShader.SetBuffer(mortonSortKernel, "mortonSortedCellHashes", mortonSortedCellHashes);
                gridShader.SetBuffer(mortonSortKernel, "cellOffsets", cellOffsets);
                gridShader.SetBuffer(mortonSortKernel, "cellCounts", cellCounts);

                int numPairs = ObiUtils.CeilToPowerOfTwo(maxCells) / 2;
                int numStages = (int)Mathf.Log(numPairs * 2, 2);
                int groups = ComputeMath.ThreadGroupCount(numPairs, 128);

                for (int stageIndex = 0; stageIndex < numStages; stageIndex++)
                {
                    for (int stepIndex = 0; stepIndex < stageIndex + 1; stepIndex++)
                    {
                        int groupWidth = 1 << (stageIndex - stepIndex);
                        int groupHeight = 2 * groupWidth - 1;
                        gridShader.SetInt("groupWidth", groupWidth);
                        gridShader.SetInt("groupHeight", groupHeight);
                        gridShader.SetInt("stepIndex", stepIndex);
                        gridShader.Dispatch(mortonSortKernel, groups, 1, 1);
                    }
                }

                // build morton indices:
                gridShader.SetBuffer(buildMortonKernel, "mortonSortedCellHashes", mortonSortedCellHashes);
                gridShader.SetBuffer(buildMortonKernel, "cellHashToMortonIndex", cellHashToMortonIndex);
                gridShader.Dispatch(buildMortonKernel, cellThreadGroups, 1, 1);

                // prefix sum to build cell start array: 
                cellsPrefixSum.Sum(cellCounts, cellOffsets);

                // sort simplex indices and compact fluid simplex indices:
                gridShader.SetBuffer(sortSimplicesKernel, "phases", solver.phasesBuffer);
                gridShader.SetBuffer(sortSimplicesKernel, "cellHashToMortonIndex", cellHashToMortonIndex);
                gridShader.SetBuffer(sortSimplicesKernel, "sortedSimplexIndices", sortedSimplexIndices);
                gridShader.SetBuffer(sortSimplicesKernel, "sortedFluidIndices", sortedFluidIndices);
                gridShader.SetBuffer(sortSimplicesKernel, "R_offsetInCell", offsetInCell);
                gridShader.SetBuffer(sortSimplicesKernel, "R_cellOffsets", cellOffsets);
                gridShader.SetBuffer(sortSimplicesKernel, "R_cellCoords", solver.cellCoordsBuffer);
                gridShader.SetBuffer(sortSimplicesKernel, "simplices", solver.simplices);
                gridShader.Dispatch(sortSimplicesKernel, simplexThreadGroups, 1, 1);

                // prefix sum of fluid flags:
                fluidPrefixSum.Sum(sortedFluidIndices, sortedSimplexToFluid);

                // build fluid dispatch buffer:
                gridShader.SetBuffer(buildFluidDispatchKernel, "fluidDispatchBuffer", solver.fluidDispatchBuffer);
                gridShader.SetBuffer(buildFluidDispatchKernel, "sortedFluidIndices", sortedFluidIndices);
                gridShader.SetBuffer(buildFluidDispatchKernel, "sortedSimplexToFluid", sortedSimplexToFluid);
                gridShader.Dispatch(buildFluidDispatchKernel, 1, 1, 1);

                // sort fluid data:
                gridShader.SetBuffer(sortFluidSimplicesKernel, "sortedFluidIndices", sortedFluidIndices);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "cellHashToMortonIndex", cellHashToMortonIndex);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "positions", solver.positionsBuffer);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "fluidInterface", solver.fluidInterfaceBuffer);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "principalRadii", solver.principalRadiiBuffer);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "phases", solver.phasesBuffer);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "sortedPositions", sortedPositions);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "sortedFluidMaterials", sortedFluidMaterials);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "sortedFluidInterface", sortedFluidInterface);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "sortedPrincipalRadii", sortedPrincipalRadii);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "fluidMaterials", solver.fluidMaterialsBuffer);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "R_offsetInCell", offsetInCell);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "R_cellOffsets", cellOffsets);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "R_cellCoords", solver.cellCoordsBuffer);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "sortedSimplexToFluid", sortedSimplexToFluid);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "simplices", solver.simplices);
                gridShader.SetBuffer(sortFluidSimplicesKernel, "fluidDispatchBuffer", solver.fluidDispatchBuffer);
                gridShader.Dispatch(sortFluidSimplicesKernel, simplexThreadGroups, 1, 1);
            }
        }

        public void GenerateContacts(ComputeSolverImpl solver)
        {
            if (solver.simplexCounts.simplexCount > 0)
            {
                int simplexThreadGroups = ComputeMath.ThreadGroupCount(solver.simplexCounts.simplexCount, 128);

                // generate contacts list:
                gridShader.SetBuffer(contactsKernel, "simplices", solver.simplices);
                gridShader.SetBuffer(contactsKernel, "sortedSimplexIndices", sortedSimplexIndices);
                gridShader.SetBuffer(contactsKernel, "sortedPositions", sortedPositions);
                gridShader.SetBuffer(contactsKernel, "sortedFluidMaterials", sortedFluidMaterials);
                gridShader.SetBuffer(contactsKernel, "positions", solver.positionsBuffer);

                gridShader.SetBuffer(contactsKernel, "cellHashToMortonIndex", cellHashToMortonIndex);
                gridShader.SetBuffer(contactsKernel, "restPositions", solver.restPositionsBuffer);
                gridShader.SetBuffer(contactsKernel, "orientations", solver.orientationsBuffer);
                gridShader.SetBuffer(contactsKernel, "restOrientations", solver.restOrientationsBuffer);
                gridShader.SetBuffer(contactsKernel, "velocities", solver.velocitiesBuffer);
                gridShader.SetBuffer(contactsKernel, "invMasses", solver.invMassesBuffer);
                gridShader.SetBuffer(contactsKernel, "phases", solver.phasesBuffer);
                gridShader.SetBuffer(contactsKernel, "filters", solver.filtersBuffer);
                gridShader.SetBuffer(contactsKernel, "principalRadii", solver.principalRadiiBuffer);
                gridShader.SetBuffer(contactsKernel, "normals", solver.normalsBuffer);
                gridShader.SetBuffer(contactsKernel, "R_cellCoords", solver.cellCoordsBuffer);
                gridShader.SetBuffer(contactsKernel, "R_cellOffsets", cellOffsets);
                gridShader.SetBuffer(contactsKernel, "R_cellCounts", cellCounts);
                gridShader.SetBuffer(contactsKernel, "R_offsetInCell", offsetInCell);
                gridShader.SetBuffer(contactsKernel, "R_levelPopulation", levelPopulation);
                gridShader.SetBuffer(contactsKernel, "particleContacts", solver.abstraction.particleContacts.computeBuffer);
                gridShader.SetBuffer(contactsKernel, "contactPairs", contactPairs);
                gridShader.SetBuffer(contactsKernel, "dispatchBuffer", dispatchBuffer);
                gridShader.Dispatch(contactsKernel, simplexThreadGroups, 1, 1);
            }
        }

        public void GenerateFluidNeighborhoods(ComputeSolverImpl solver)
        {
            if (solver.simplexCounts.simplexCount > 0)
            {
                // generate fluid neighbors list:
                gridShader.SetBuffer(sameLevelNeighborsKernel, "solverBounds", solver.reducedBounds);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "cellHashToMortonIndex", cellHashToMortonIndex);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "sortedFluidIndices", sortedFluidIndices);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "sortedPositions", sortedPositions);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "sortedFluidMaterials", sortedFluidMaterials);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "R_cellCoords", solver.cellCoordsBuffer);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "R_cellOffsets", cellOffsets);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "R_cellCounts", cellCounts);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "R_offsetInCell", offsetInCell);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "R_levelPopulation", levelPopulation);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "sortedSimplexToFluid", sortedSimplexToFluid);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "simplices", solver.simplices);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "neighbors", neighbors);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "neighborCounts", neighborCounts);
                gridShader.SetBuffer(sameLevelNeighborsKernel, "dispatchBuffer", solver.fluidDispatchBuffer);
                gridShader.DispatchIndirect(sameLevelNeighborsKernel, solver.fluidDispatchBuffer);

                gridShader.SetBuffer(upperLevelsNeighborsKernel, "solverBounds", solver.reducedBounds);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "cellHashToMortonIndex", cellHashToMortonIndex);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "sortedPositions", sortedPositions);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "sortedFluidIndices", sortedFluidIndices);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "mortonSortedSimplexIndices", mortonSortedCellHashes);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "sortedFluidMaterials", sortedFluidMaterials);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "R_cellCoords", solver.cellCoordsBuffer);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "R_cellOffsets", cellOffsets);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "R_cellCounts", cellCounts);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "R_offsetInCell", offsetInCell);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "R_levelPopulation", levelPopulation);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "sortedSimplexToFluid", sortedSimplexToFluid);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "simplices", solver.simplices);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "neighbors", neighbors);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "neighborCounts", neighborCounts);
                gridShader.SetBuffer(upperLevelsNeighborsKernel, "dispatchBuffer", solver.fluidDispatchBuffer);
                gridShader.DispatchIndirect(upperLevelsNeighborsKernel, solver.fluidDispatchBuffer);

                gridShader.SetBuffer(buildFluidIndexBufferKernel, "sortedFluidIndices", sortedFluidIndices);
                gridShader.SetBuffer(buildFluidIndexBufferKernel, "simplices", solver.simplices);
                gridShader.SetBuffer(buildFluidIndexBufferKernel, "dispatchBuffer", solver.fluidDispatchBuffer);
                gridShader.DispatchIndirect(buildFluidIndexBufferKernel, solver.fluidDispatchBuffer);
            }
        }
    }
}
